--- title: "04-Redis 持久化与高可用" created: 2026-08-31 tags: - 项目筑基 --- # Redis 持久化与高可用 > Redis 系统讲解第三篇:数据怎么保住、服务怎么不倒。01 篇有 RDB/AOF 的对照结论表,这一篇讲机制细节——fork 与写时复制、AOF 重写、主从同步的全量/增量边界、哨兵的判定与选主、分片槽的迁移。 ## RDB:fork + 写时复制 `bgsave` 的完整流程: ```text 主进程 fork 出子进程 → 子进程遍历内存生成 RDB 快照写入临时文件 → 完成后原子替换旧 RDB ``` 关键机制是**写时复制(COW)**:fork 后父子共享同一份物理内存页;主进程继续处理写请求,**只有被修改的页才会复制一份新页**。所以: - bgsave 期间 Redis 照常服务(子进程看到的是 fork 瞬间的数据快照) - 写入量大的瞬间,内存占用可能**瞬间翻倍**——`maxmemory` 要留余量(实际用量的 2 倍左右是常见建议) - fork 本身会阻塞主进程(复制页表,实例越大越久)——10GB 实例 fork 可能卡几百毫秒 自动触发:配置里的 `save 900 1`(900 秒内 1 次修改)等条件;`shutdown` 时若无 AOF 也会触发。 ## AOF:三种刷盘与重写 | appendfsync | 时机 | 丢失窗口 | 性能 | | --- | --- | --- | --- | | always | 每条命令都 fsync | 不丢 | 最差 | | **everysec**(默认) | 后台线程每秒 fsync | 最多 1 秒 | 推荐 | | no | 交给操作系统 | 不确定 | 最快最险 | **AOF 文件太大怎么办——重写(bgrewriteaof)**:fork 子进程根据当前内存数据生成一份"最简命令集"(比如对同一个 key 的 100 次 INCR 重写成一条 SET),期间新写入进 AOF 重写缓冲区,重写完成后追加替换。效果类似"日志压实",不读旧 AOF、直接以内存现状为准。 **混合持久化(4.0+,`aof-use-rdb-preamble yes`)**:重写时先写 RDB 格式的全量,再追加增量命令——**恢复快(RDB 打底)+ 丢数据少(AOF 增量)**,生产默认开。 ## 主从复制:全量与增量的分界 ```text 首次同步(全量): 从库发 psync ? -1 → 主库 bgsave 生成 RDB → 传输 RDB → 从库载入 → 期间新写命令缓存在 repl_backlog → RDB 加载完后补发缓冲区 断线重连(增量): 从库带上自己已同步的偏移量 offset → 主库比对 repl_backlog: offset 还在缓冲区里 → 只发缺失部分(增量) 已被环形覆盖 → 退回全量同步 ``` **repl_backlog_size 是关键调优项**:缓冲区太小,网络一抖动就"被迫全量"——全量同步很重(fork + 传输 + 从库清空重载),生产要按写入速率和网络质量调大。 细节清单:主从都可用 `INFO replication` 看状态;从库默认只读;复制是异步的(主不等从,主挂了未同步的数据会丢——这就是 writeConcern 式取舍在 Redis 里的体现,用 `WAIT` 命令可要求确认)。 ## 哨兵:三个任务与两次判定 哨兵集群(至少 3 个、**奇数**部署)干三件事:**监控、选主、通知**。 ```text 主观下线:单个哨兵 ping 不通主库 → 标记 sdown 客观下线:≥ quorum 个哨兵都认为 sdown → 升级 odown → 启动选举 选主:哨兵之间 Raft 式投票选出领头哨兵 → 它按规则挑新主(从库优先级/复制偏移量/runid)→ 通知客户端 ``` 我的记忆锚点:**quorum 是"判定门槛",不是"投票人数"**——判定主挂了要 quorum 票,选领头哨兵要多数派,两套票分开理解。 客户端用法变化:连的是哨兵 asking "谁是主",而不是写死主地址——Redis 客户端(redis-py 的 `Sentinel` 类、Java 的 Lettuce)都内置了这套发现逻辑。 ## 分片集群:槽与迁移 - 16384 个槽,`slot = CRC16(key) % 16384`;节点负责一部分槽 - 客户端直连任意节点:key 不在该节点 → 返回 **MOVED**(客户端缓存槽位表,下次直连正确节点) - 迁移期间的半成品 key:返回 **ASK**(本次先去目标节点问,不更新客户端缓存)——MOVED 和 ASK 的区别是书里高频考点 - 批量操作限制:mget 跨槽会报错——**key 里带 hash tag**(如 `user:{100}:profile`,花括号内才参与计算)可把相关 key 钉进同一槽 - 多 key 事务/Lua 同样受槽限制:同一 Lua 的所有 key 必须同槽 > 💡 高可用形态选择锚点:数据量小、读压力大 → 主从 + 哨兵就够;写也扛不住或数据到几十 GB → 分片集群。集群模式下的多 key 操作、事务、Lua 都受限,能用单机/主从解决的别急着上分片。 --- ⬅️ [[03-Redis 应用实战|03-Redis 应用实战]] 🏠 [[00-数据库|00-数据库]] ➡️ [[05-Redis 底层原理与性能|05-Redis 底层原理与性能]]